package com.cy.pj.sys.common.config;

import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**
 * @author 张琪
 * @date 2020/11/19
 * @describe
 */

// @Configuration 注解描述的类为Spring中的一个配置类, 也会交给Spring管理
@Configuration
public class SpringShiroConfig {

    /**
     * 构建并初始化SecurityManager对象, 然后交给Spring管理
     * @return 返回值为shiro中的安全管理器对象, 是shiro框架的核心,此对象中实现了
     * 认证, 授权, 回话, 缓存, 加密等一系列功能的实现
     */
    /*@Bean 注解 应用与@Configuration注解描述的类中, 通过该注解描述的方法,方法的返回值会交给spring管理
    * 默认bean的名字为方法名*/
    @Bean
    public SecurityManager securityManager(Realm realm, CacheManager cacheManager,
                                           RememberMeManager rememberMeManager,
                                            SessionManager sessionManager){
        DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();

        sManager.setRealm(realm);
        /*在shiro中还可以使用第三方的缓存产品(Redis) 来缓存用户信息*/
        sManager.setCacheManager(cacheManager);
//        记住我配置
        sManager.setRememberMeManager(rememberMeManager);
//        会话管理配置
//        sManager.setSessionManager(sessionManager);
        return sManager;
    }


    /**
     * 构建并初始化shiroFilterFactoryBean对象, 通过此对象创建过滤器工厂
     * 再根据工厂创建过滤器(filter), 并通过过滤器对请求信息进行过滤.
     * 例如检测此请求是否需要认证 或 此请求是否已经认证
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean fBean = new ShiroFilterFactoryBean();
//        设置securityManager, 基于此对象进行认证检测
        fBean.setSecurityManager(securityManager);
        fBean.setLoginUrl("/doLoginUI");
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        /*anon 表示可以匿名访问*/
        map.put("/bower_components/**","anon");
        map.put("/build/**","anon");
        map.put("/dist/**","anon");
        map.put("/plugins/**","anon");

        map.put("/user/doLogin","anon");        //登陆操作

        map.put("/doLogout","logout");          //登出操作, 一定要使用logout,shiro帮我们将token从subject中抛出


        /*authc 表示认证后才能访问,  需要认证才能访问的资源一定要写在匿名可以访问资源的后面
        * user 表示可以从用户浏览器中读取cookie来进行身份认证*/
        map.put("/**", "user"); // 修改前 : authc
//          定义map指定请求过滤规则
        fBean.setFilterChainDefinitionMap(map);
        return fBean;
    }


    /* 配置授权的Advisor(顾问)对象, 此对象会在Spring启动时加载, 并且通过此对象可以
    * 找到@RequiresPermissions 注解描述的方法, 当这些方法运行时, 由此advisor对象调用
    * SecurityManager中的(checkPermissions)方法 来为 访目标切入点方法的用户做授权操作 */
    @Bean
    public AuthorizationAttributeSourceAdvisor
    authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor =
                new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }


    /*在SpringShiroConfig中配置缓存对象,  方法名不能叫cacheManager, 因为容器中已经存在了该名字的bean
    * 此对象管理着一个cache对象, 此cache可以存储授权是获取的用户权限信息, 下次在授权时可以直接从缓存取
    * 用户权限, 这样可以减少对数据库的访问压力, 提高其授权性能.
    * SecurityManager来管理 cacheManager 来完成上述功能*/
    @Bean
    public CacheManager shiroCacheManager(){
        return new MemoryConstrainedCacheManager();
    }


    /*添加记住我配置*/
    @Bean
    public RememberMeManager rememberMeManager(){
        CookieRememberMeManager cManager =
                new CookieRememberMeManager();
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setMaxAge(7*24*60*60);       //设置有效声明周期
        cManager.setCookie(cookie);
        return cManager;
    }

//    会话管理配置: 服务器端记录会话端的记录

    /**
     * Session 是什么? 此对象是在服务端记录客户端与服务端 会话状态 的 一个对象
     * 这个对象一般是一个会话创建一个, 并且会有一个唯一标识 JSESSIONID
     * 可以通过此对象来记录用户登陆信息(服务器默认记录30min)
     * 还可以记录购物车信息, 验证码信息
     * @return
     */
    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sManager =
                new DefaultWebSessionManager();
        sManager.setGlobalSessionTimeout(60*60*1000); //设置时间超过一个小时后删除

        /*
        * session对象由  服务器创建
        * 但这个对象创建好以后, 会将其jsesssion 以cookie的形式存储到浏览器中
        * 客户端再次访问服务器是, 会携带就jsession到服务器, 然后基于这个id找到session*/
//        下面的语句 关闭了jsession 以url的形式传递到服务器中
        sManager.setSessionIdUrlRewritingEnabled(false);    //关闭url重写
//        (浏览器禁用cookie时)重写url时, 会在url的后面添加一个jsessionid,
//        服务端可以基于这个id为客户端找到对应的session
        return sManager;
    }

    /**
     * 1. http协议是一种无状态协议, 无法存储客户端与服务端的会话状态,
     * 2. 客户端与服务端通讯过程中产生的会话状态信息存储到哪里.(cookie & session)
     * 5. 会话cookie在浏览器关闭时, 生命周期自动结束; 持久cookie根据设置的时间来结束它的生命周期
     * 6. session对象由服务器创建, 并在服务端保存状态信息的一个对象
     * 7. session对象创建以后会将session对象的id以会话cookie的形式存储到客户端, 在下一次的访问时,
     *    基于这个id找到服务端的session
     * 8. 浏览器关闭, 会话cookie结束, 写到客户端jsession也就无效了, 此时客户端在访问服务器无法基
     *    jsession找到对应的session了(服务端的session可能还存在)
     */
}
